package br.mikhas.util;

import static br.mikhas.util.Assert.notNull;

import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Util methods to help creating collections
 * 
 * @author Mikhail Domanoski
 * 
 */
public class CollectionUtils {

	/**
	 * Checks if the providen collection is empty
	 * 
	 * @param collection
	 *            The collection to check
	 * @return returns <code>true</code> if the collection is empty or
	 *         <code>null</code>
	 */
	public static boolean isEmpty(Collection<?> collection) {
		return collection == null || collection.isEmpty();
	}

	/**
	 * Checks if the providen map is empty
	 * 
	 * @param map
	 *            The map to check
	 * @return returns <code>true</code> if the map is empty or
	 *         <code>null</code>
	 */
	public static boolean isEmpty(Map<?, ?> map) {
		return map == null || map.isEmpty();
	}

	/**
	 * Checks if the providen array is empty
	 * 
	 * @param array
	 *            The array to check
	 * @return returns <code>true</code> if the array is empty or
	 *         <code>null</code>
	 */
	public static boolean isEmpty(Object[] array) {
		return array == null || array.length == 0;
	}

	/**
	 * Creates a list from an array of values
	 * 
	 * @param <T>
	 *            The type of the elements on the list
	 * @param values
	 *            The values to be added on the list
	 * @return A list with all values on the array
	 */
	public static <T> List<T> list(T... values) {
		return Arrays.asList(values);
	}

	/**
	 * Creates a set with the values passed on the array
	 * 
	 * @param <T>
	 *            The type of the elements on the array
	 * @param values
	 *            The values to be added on the set
	 * @return The set
	 */
	public static <T> Set<T> set(T... values) {
		Set<T> set = new HashSet<T>(values.length);

		for (int i = 0; i < values.length; i++)
			set.add(values[i]);

		return set;
	}

	/**
	 * Creates a set with the values passed on the colection
	 * 
	 * @param <T>
	 *            The type of the elements on the array
	 * @param values
	 *            The values to be added on the set
	 * @return The set
	 */
	public static <T> Set<T> set(Collection<T> values) {
		if (values == null)
			return null;

		if (values instanceof Set<?>)
			return (Set<T>) values;

		Set<T> set = new HashSet<T>(values.size());

		for (T value : values) {
			set.add(value);
		}
		return set;
	}

	/**
	 * Creates a map from an array of key-value entries
	 * 
	 * @param <K>
	 *            The type of the key of the pair
	 * @param <V>
	 *            The type of the value element
	 * @param args
	 *            an array with even number of values which should represent a
	 *            key-value pair.
	 *            <p>
	 *            Ex.: map(key1, value1, key2, value2, key3, value3, ...)
	 * @return A map with the entries
	 */
	@SuppressWarnings("unchecked")
	public static <K, V> Map<K, V> map(Object... args) {

		if (args.length % 2 != 0)
			throw new IllegalArgumentException(
					"Illegal number of key-value pair entries");

		Map<K, V> map = new HashMap<K, V>(args.length / 2);

		for (int i = 0; i < args.length; i = i + 2) {
			K key = (K) args[i];
			V value = (V) args[i + 1];
			map.put(key, value);
		}

		return map;

	}

	/**
	 * Turns an {@link Enumeration} into an {@link Iterator} which is easier to
	 * handle
	 * 
	 * @param <T>
	 *            The type of objects stored in the {@link Enumeration}
	 * @param enumeration
	 *            The numeration object to be converted
	 * @return An iterator which handles the enumeration
	 */
	public static <T> Iterable<T> toIterable(Enumeration<T> enumeration) {
		notNull(enumeration);
		return new EnumerationIterable<T>(enumeration);
	}

	/**
	 * Internal class to wrap a {@link Enumeration} into an {@link Iterable}
	 * 
	 * @author Mikhail Domanoski
	 * 
	 * @param <T>
	 *            The type of the iterable element
	 */
	private static class EnumerationIterable<T> implements Iterable<T> {

		private final Enumeration<T> enumeration;

		EnumerationIterable(Enumeration<T> enumeration) {
			this.enumeration = enumeration;
		}

		@Override
		public Iterator<T> iterator() {
			return new EnumerationIterator<T>(enumeration);
		}

		@Override
		public String toString() {
			return this.enumeration.toString();
		}
	}

	/**
	 * Internal class to wrap a {@link Enumeration} into an {@link Iterator}
	 * 
	 * @author Mikhail Domanoski
	 * 
	 * @param <T>
	 *            The type of the iterating element
	 */
	private static class EnumerationIterator<T> implements Iterator<T> {

		private final Enumeration<T> enumeration;

		EnumerationIterator(Enumeration<T> enumeration) {
			this.enumeration = enumeration;
		}

		@Override
		public boolean hasNext() {
			return this.enumeration.hasMoreElements();
		}

		@Override
		public T next() {
			return this.enumeration.nextElement();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException(
					"Remove operation is not supported.");
		}

		@Override
		public String toString() {
			return this.enumeration.toString();
		}

	}
}
